%{
/*
 * SCPI 指令词法解析器
 * @author 张勇 2023-12
 * 
 * 使用 flex (>=2.6.1) 生成 C 代码(release 版):
 *   flex --noline -o=scpi_lexer.c scpi.l
 *
 * 使用 flex (>=2.6.1) 生成 C 代码(debug 版, 便于调试词法分析器):
 *   flex --yylineno -DYYDEBUG=1 -o=scpi_lexer.c scpi.l
 */

#ifdef _MSC_VER
	#include <io.h>
#elif (__linux__)
	#include <sys/io.h>
#endif

#include "share/std_err.h"
#include "share/utils.h"
#include "scpi_lexer.h"

#include "share/log.h"
// 在包含用户日志头文件 "log4app.h" 之前，应先定义 LOGLEVEL 和 LOGTAG
#if YYDEBUG
#define LOGLEVEL LOG_DEBUG
#else
#define LOGLEVEL LOG_ERR
#endif // YYDEBUG
#define LOGTAG   "SCPI-Lexer"
#include "share/log4app.h"

#ifdef _MSC_VER
#define YY_NO_UNISTD_H
#endif

// 获取状态码对应的状态名
const char *state_name(int state);
// 老的状态
static int old_state = 0;
// 开始新的状态
#define state_begin(s) BEGIN(s)
// 保存旧的状态，开始新的状态
#define state_push(s) (old_state = YY_START, BEGIN(s)/*, printf("state_push: %s -> %s\r\n", state_name(old_state), state_name(s))*/)
// 恢复之前的状态
#define state_pop() (/*printf("state_pop: %s -> %s\r\n", state_name(YY_START), state_name(old_state)), */BEGIN(old_state))

#if YYDEBUG
	// 当前 token 对应的列号
	static int yycolumn = 1;
	#define NEWLINE() yycolumn = 1
	// YY_USER_ACTION 宏在每个 Rule-Action 执行前都会执行, 这里用于跟踪计算列号
	#define YY_USER_ACTION  yycolumn += yyleng;
	// 用于调试跟踪 Action
	#define DBGACT() logd("(%s) %d:%d+%d, @%s: %d\r\n", state_name(YY_START), yylineno, yycolumn, yyleng, __FILE__, __LINE__)
#else
	#define yycolumn 0
	#define NEWLINE()
	#define YY_USER_ACTION
	#define DBGACT()
#endif

// 当前正在解析的 SCPI 命令组
static scpi_cmd_group_t* _group = NULL;
// 错误缓冲区
static char* _err_buf = NULL;
static size_t _err_buf_size = 0;

static std_err_t error_stop(std_err_t err, const char *reason, const char *text, int lineno, int column, int len) {
	loge("Error stop: (%s) %s at @%d:%d+%d: '%s'\r\n", state_name(YYSTATE), reason, lineno, column, len, text);
	if (_err_buf && _err_buf_size > 0) {
		snprintf(_err_buf, _err_buf_size, "Error stop: (%s) %s at @%d:%d+%d: '%s'\r\n", state_name(YYSTATE), reason, lineno, column, len, text);
	}
	return err;
}

// 检查错误，如果有错误就返回，否则继续
#define CHKERR(x) do {int _errx = (x); if(_errx) return error_stop(_errx, std_err_comment(_errx), yytext, yylineno, yycolumn, yyleng); } while(0)

// 生成一个新的子命令
static std_err_t new_cmd_part(const char *text, int lineno, int column, int len) {
	logd("  new_cmd_part: '%s' @%d:%d+%d\r\n", text, lineno, column, len);
	if (!_group) return STD_ERR_INVALID_STATE;
	
	// 获取当前正在解析的 SCPI 命令
	scpi_cmd_t* cmd = &(_group->cmds[_group->cmd_count]);
	// 获取当前正在解析的 SCPI 命令字段
	if (cmd->part_count >= SCPI_CMD_PART_COUNT) return STD_ERR_TOO_MUCH_CMDPARTS;
	scpi_cmd_part_t* part = &(cmd->parts[cmd->part_count]);
	
	if (text[0] == '*') {
		if(cmd->part_count == 0) {
			// 如果是第一个命令字段，且第一个字符是 '*' 则表示是通用命令
			cmd->is_asterisk = true;
			// 去掉 '*'
			text++;
		} else {
			// 非法字符
			return STD_ERR_ILLEGAL_CHAR;
		}
	}
	// 保存命令字段名称
	strncpy(part->str, text, sizeof(part->str));
	// 命令字段数量 +1
	cmd->part_count++;
	return STD_ERR_OK;
}

// 生成一个新的命令参数
static std_err_t new_arg(const char *text, int lineno, int column, int len) {
	logd("  new_arg: '%s' @%d:%d+%d\r\n", text, lineno, column, len);
	if (!_group) return STD_ERR_INVALID_STATE;

	// 获取当前正在解析的 SCPI 命令
	scpi_cmd_t* cmd = &(_group->cmds[_group->cmd_count]);
	// 获取当前正在解析的 SCPI 命令参数
	if (cmd->arg_count >= SCPI_CMD_ARG_COUNT) return STD_ERR_TOO_MUCH_CMDARGS;
	scpi_cmd_arg_t* arg = &(cmd->args[cmd->arg_count]);

	// 保存命令参数字符串
	strncpy(arg->str, text, sizeof(arg->str));
	// 命令参数数量 +1
	cmd->arg_count++;
	return STD_ERR_OK;
}

// 设置当前命令为查询命令; 命令部分结束，开始参数部分
static std_err_t set_as_query_cmd(const char *text, int lineno, int column, int len) {
	logd("  set_as_query_cmd: '%s' @%d:%d+%d\r\n", text, lineno, column, len);
	if (!_group) return STD_ERR_INVALID_STATE;

	// 获取当前正在解析的 SCPI 命令
	scpi_cmd_t* cmd = &(_group->cmds[_group->cmd_count]);
	// 设置当前命令为查询命令
	cmd->is_query = true;
	return STD_ERR_OK;
}

// 清除当前字符串参数
static std_err_t string_arg_begin(int lineno, int column) {
	logd("  string_arg_begin: @%d:%d\r\n", lineno, column);
	if (!_group) return STD_ERR_INVALID_STATE;

	// 获取当前正在解析的 SCPI 命令
	scpi_cmd_t* cmd = &(_group->cmds[_group->cmd_count]);
	// 获取当前正在解析的 SCPI 命令参数
	if (cmd->arg_count >= SCPI_CMD_ARG_COUNT) return STD_ERR_TOO_MUCH_CMDARGS;
	scpi_cmd_arg_t* arg = &(cmd->args[cmd->arg_count]);

	// 清除当前字符串参数
	memset(arg->str, 0, sizeof(arg->str));
	return STD_ERR_OK;
}

static std_err_t string_arg_append(const char *str) {
	//logd("  string_append: %s\r\n", str);
	if (!_group) return STD_ERR_INVALID_STATE;

	// 获取当前正在解析的 SCPI 命令
	scpi_cmd_t* cmd = &(_group->cmds[_group->cmd_count]);
	// 获取当前正在解析的 SCPI 命令参数
	scpi_cmd_arg_t* arg = &(cmd->args[cmd->arg_count]);
	
	// 检查字符串长度
	size_t len = strlen(arg->str);
	size_t remain = sizeof(arg->str) - len - 1;
	if (strlen(str) >= remain) return STD_ERR_TOO_LARGE_STR;
	// 向字符串参数中添加字符串
	strncpy(&arg->str[len], str, remain);
	return STD_ERR_OK;
}

static std_err_t string_arg_putchar(char ch) {
	//logd("  string_putchar: %c\r\n", ch);
	if (!_group) return STD_ERR_INVALID_STATE;

	// 获取当前正在解析的 SCPI 命令
	scpi_cmd_t* cmd = &(_group->cmds[_group->cmd_count]);
	// 获取当前正在解析的 SCPI 命令参数
	scpi_cmd_arg_t* arg = &(cmd->args[cmd->arg_count]);

	// 检查字符串长度
	size_t len = strlen(arg->str);
	if (len >= sizeof(arg->str)-1) return STD_ERR_TOO_LARGE_STR;
	// 向字符串参数中添加字符
	arg->str[len] = ch;
	arg->str[len + 1] = '\0';
	return STD_ERR_OK;
}

static std_err_t string_arg_close(int lineno, int column) {
	logd("  string_close: @%d:%d\r\n", lineno, column);
	if (!_group) return STD_ERR_INVALID_STATE;

	// 获取当前正在解析的 SCPI 命令
	scpi_cmd_t* cmd = &(_group->cmds[_group->cmd_count]);
	// 获取当前正在解析的 SCPI 命令参数
	scpi_cmd_arg_t* arg = &(cmd->args[cmd->arg_count]);
	
	// 设置参数类型为“带引号的字符串”
	arg->is_string = true;
	// 命令参数数量 +1
	cmd->arg_count++;
	return STD_ERR_OK;
}

// 一组命令结束，回到初始状态
static std_err_t seal_cmd() {
	logd("  seal_cmd\r\n");
	if (!_group) return STD_ERR_INVALID_STATE;
	
	// 获取当前正在解析的 SCPI 命令
	scpi_cmd_t* cmd = &(_group->cmds[_group->cmd_count]);
	// 如果当前命令有效，则命令个数 +1
	if (cmd->part_count > 0) _group->cmd_count++;
	return STD_ERR_OK;
}

// 一组命令结束，回到初始状态
static std_err_t seal_group() {
	logd("  seal_group\r\n");
	if (!_group) return STD_ERR_INVALID_STATE;
	return STD_ERR_OK;
}

/**
 * 当lex读取到文件末尾时，会调用yywrap(), 
 * 目的是，当有另外一个输入文件时，yywrap可以调整yyin的值并且返回0来重新开始词法分析。
 * 如果是真正的文件末尾，那么就返回1来完成分析。
 */
static int yywrap(void) {
	return 1;
}

%}

%option outfile="scpi_lexer.c"

  /* 定义一些正则表达式别名 */
LineTerminators         (\r|\n)+
WhiteSpaces				[ \t]+
CppComment              \/\/[^\r\n]*
Identifiers           	\*?[a-zA-Z][a-zA-Z0-9]*
Argument                [a-zA-Z0-9_\.\+\-]+[ \t]*[a-zA-Z]*

  /* 定义几个状态 */
%s CMDPARTS ARGS
%x STR1 STR2 CCOMMENT ARG_BRACKET


%%

<INITIAL>:                      { DBGACT(); state_push(CMDPARTS); } /* 子命令状态开始 */
<INITIAL>{Identifiers}          { DBGACT(); CHKERR(new_cmd_part(yytext, yylineno, yycolumn, yyleng)); state_push(CMDPARTS); } /* 完整子命令，子命令状态开始 */
<INITIAL>;                      { DBGACT(); } /* 命令分隔符 */
<INITIAL>{WhiteSpaces}          { DBGACT(); } /* 空白字符 */
<INITIAL>{CppComment}           { DBGACT(); } /* C++ style comment */
<INITIAL>"/*"                   { DBGACT(); state_push(CCOMMENT); } /* C style comment begin */
<INITIAL>{LineTerminators}      { DBGACT(); NEWLINE(); } /* 空行 */

<CMDPARTS>:                     { DBGACT(); } /* 上一个子命令结束 */
<CMDPARTS>{Identifiers}         { DBGACT(); CHKERR(new_cmd_part(yytext, yylineno, yycolumn, yyleng)); } /* 完整子命令 */
<CMDPARTS>\?                    { DBGACT(); set_as_query_cmd(yytext, yylineno, yycolumn, yyleng); } /* 设置命令为查询命令 */
<CMDPARTS>;                     { DBGACT(); seal_cmd(); } /* 上一命令结束，开始一个新的命令 */
<CMDPARTS>{LineTerminators}     { DBGACT(); NEWLINE(); state_begin(INITIAL); seal_cmd(); seal_group(); return STD_ERR_OK; } /* 一组命令结束，回到初始状态 */
<CMDPARTS>{WhiteSpaces}         { DBGACT(); state_begin(ARGS); } /* 命令部分结束，开始参数部分 */

<ARGS>;                         { DBGACT(); seal_cmd(); state_begin(CMDPARTS); } /* 上一命令结束，开始一个新的命令 */
<ARGS>,                         { DBGACT(); } /* 上一参数结束，开始一个新的参数 */
<ARGS>{LineTerminators}         { DBGACT(); NEWLINE(); state_begin(INITIAL); seal_cmd(); seal_group(); return STD_ERR_OK; } /* 一组命令结束，回到初始状态 */
<ARGS>{CppComment}              { DBGACT(); } /* C++ style comment */
<ARGS>"/*"                      { DBGACT(); state_push(CCOMMENT); } /* C style comment begin */
<ARGS>{WhiteSpaces}             { DBGACT(); } /* 空白字符 */
<ARGS>{Argument}                { DBGACT(); CHKERR(new_arg(yytext, yylineno, yycolumn, yyleng)); }    /* 完整参数 */
<ARGS>\'                        { DBGACT(); CHKERR(string_arg_begin(yylineno, yycolumn)); state_push(STR1); } /* 单引号字符串参数开始 */
<ARGS>\"                        { DBGACT(); CHKERR(string_arg_begin(yylineno, yycolumn)); state_push(STR2); } /* 双引号字符串参数开始 */
<ARGS>\(                        { DBGACT(); CHKERR(string_arg_begin(yylineno, yycolumn)); CHKERR(string_arg_append(yytext)); state_push(ARG_BRACKET); } /* 括号包围的复合参数开始 */

<STR1>\'                        { DBGACT(); string_arg_close(yylineno, yycolumn); state_pop(); } /* 单引号字符串参数结束 */
<STR1>[^\n\r\'\\]+              { DBGACT(); CHKERR(string_arg_append(yytext)); }

<STR2>\"                        { DBGACT(); string_arg_close(yylineno, yycolumn); state_pop(); } /* 双引号字符串参数结束 */
<STR2>[^\n\r\"\\]+              { DBGACT(); CHKERR(string_arg_append(yytext)); }

<STR1,STR2>\r|\n                { DBGACT(); CHKERR(STD_ERR_UNCLOSED_STRING); }  /* 字符串参数内不能换行 */
<STR1,STR2>\\t                  { DBGACT(); CHKERR(string_arg_putchar('\t')); }	/* 转义字符处理 */ 
<STR1,STR2>\\n                  { DBGACT(); CHKERR(string_arg_putchar('\n')); }	/* 转义字符处理 */
<STR1,STR2>\\r                  { DBGACT(); CHKERR(string_arg_putchar('\r')); }	/* 转义字符处理 */
<STR1,STR2>\\\'                 { DBGACT(); CHKERR(string_arg_putchar('\'')); }	/* 转义字符处理 */
<STR1,STR2>\\\"                 { DBGACT(); CHKERR(string_arg_putchar('\"')); }	/* 转义字符处理 */
<STR1,STR2>\\                   { DBGACT(); CHKERR(string_arg_putchar('\\')); }	/* 转义字符处理 */

<CCOMMENT>[^*\n]*               { DBGACT(); } /* eat anything that's not a '*' */
<CCOMMENT>"*"+[^*/\n]*          { DBGACT(); } /* eat up '*'s not followed by '/'s */
<CCOMMENT>{LineTerminators}     { DBGACT(); NEWLINE(); }
<CCOMMENT>"*"+"/"               { DBGACT(); state_pop(); } /* C style comment end */

<ARG_BRACKET>\)                 { DBGACT(); CHKERR(string_arg_append(yytext)); string_arg_close(yylineno, yycolumn); state_pop(); } /* 括号包围的复合参数结束 */
<ARG_BRACKET>[^\n\r\)]+         { DBGACT(); CHKERR(string_arg_append(yytext)); }

<<EOF>>                         { DBGACT(); seal_cmd(); seal_group(); return STD_ERR_EOF; } /* 文件结束 */
.                               { DBGACT(); CHKERR(STD_ERR_ILLEGAL_CHAR); } /* error fallback */

	
%%

static struct {
	int state;
	const char *name;
} _state_names[] = {
	{INITIAL, "INITIAL"},
	{CMDPARTS, "CMDPARTS"},
	{ARGS, "ARGS"},
	{STR1, "STR1"},
	{STR2, "STR2"},
	{CCOMMENT, "CCOMMENT"},
	{ARG_BRACKET, "ARG_BRACKET"},	
};

const char *state_name(int state) {
	for(int i=0; i < COUNT(_state_names); i++) {
		if(_state_names[i].state == state) return _state_names[i].name;
	}
	return "unknown";
}

void scpi_dump_cmd_group(const scpi_cmd_group_t* group) {
	if (!group) return;
	const scpi_cmd_t* cmd;

	printf("\r\nTotal cmd: %d\r\n", group->cmd_count);
	for (int ci = 0; ci < group->cmd_count; ci++) {
		printf("  cmd[%d] = ", ci);
		cmd = &(group->cmds[ci]);

		if (cmd->is_asterisk) fputc('*', stdout);

		for (int pi = 0; pi < cmd->part_count; pi++) {
			if (pi) fputc(':', stdout);
			fputs(cmd->parts[pi].str, stdout);
		}

		if (cmd->is_query) fputc('?', stdout);

		for (int ai = 0; ai < cmd->arg_count; ai++) {
			fputs((ai ? " ," : " "), stdout);
			if(cmd->args[ai].is_string) fputc('"', stdout);
			fputs(cmd->args[ai].str, stdout);
			if(cmd->args[ai].is_string) fputc('"', stdout);
		}
		
		printf("\r\n");
	}
}

std_err_t scpi_parse_prepare(const char* data, size_t datalen, void **pyybuf) {
	if(!data || !datalen || !pyybuf) return STD_ERR_INVALID_ARG;
	std_err_t err = STD_ERR_OK;

	// 分配错误消息缓冲区
	_err_buf = malloc(128);
	if(!_err_buf) { err = STD_ERR_NO_MEM; goto _final; }
	memset(_err_buf, 0, 128);
	_err_buf_size = 128;

	// 分配 SCPI 指令组缓冲区
	_group = malloc(sizeof(scpi_cmd_group_t));
	if(!_group) { err = STD_ERR_NO_MEM; goto _final; }

	// 字符串的最后应该有 2 个 '\0' 做为结束，实际解析长度仍为 strlen(scpi)
	//*pyybuf = yy_scan_buffer((char*)scpi, strlen(scpi) + 2);
	// 直接解析字符串，解析器会自己复制字符串，并在最后添加 2 个 '\0' 做为结束
	*pyybuf = yy_scan_bytes(data, datalen);
	if(!*pyybuf) { err = STD_ERR_INVALID_ARG; goto _final; }
	// 状态机恢复到初始状态
	state_begin(INITIAL);

	return STD_ERR_OK;

_final:
	if(_err_buf) { free(_err_buf); _err_buf = NULL; _err_buf_size = 0; }
	if(_group) { free(_group); _group = NULL; }
	return err;
}

std_err_t scpi_parse_next(scpi_cmd_group_t **pcmds) {
	// 清除上次解析的 SCPI 命令组
	memset(_group, 0, sizeof(*_group));

	// !!! 启动解析 !!!
	std_err_t err = yylex();
	if (err && err != STD_ERR_EOF) {
		// SCPI 解析失败，不再继续解析剩下的 SCPI 指令？
		loge("SCPI parse failed ! err = %d\r\n", err);
		return err;
	}
	if (_group->cmd_count < 1) {
		if (!err) {
			// 不会这样吧！怎么会没有解析到 SCPI 指令又不报错？
			loge("Why there is no SCPI ?!\r\n");
			return STD_ERR_INVALID_STATE;
		}
	} else {
		// 返回解析出来的 SCPI 命令组
		if(pcmds) *pcmds = _group;
	}
	return STD_ERR_OK;
}

void scpi_parse_finish(void *yybuf) {
	if(_err_buf) { free(_err_buf); _err_buf = NULL; _err_buf_size = 0; }
	if(_group) { free(_group); _group = NULL; }
	if(yybuf) { yy_delete_buffer(yybuf); yybuf = NULL; }
}

std_err_t scpi_parse(const char* data, size_t datalen, scpi_cmd_group_handler_t callback, void *cookie) {
	if(!data || !datalen || !callback) return STD_ERR_INVALID_ARG;
	YY_BUFFER_STATE yybuf = NULL;
	scpi_cmd_group_t* cmds = NULL;
	std_err_t err = STD_ERR_OK;

	err = scpi_parse_prepare(data, datalen, (void**)&yybuf);
	if (err) return err;
	
	while(1) {
		// !!! 启动解析 !!!
		err = scpi_parse_next(&cmds);
		if (err) break;

		// !!! 调用回调函数执行解析出来的 SCPI 命令组 !!!
		err = callback(cmds, cookie);
		if (err) {
			// SCPI 指令执行失败，不再继续解析剩下的 SCPI 指令？
			loge("SCPI execute failed ! err = %d\r\n", err);
			break;
		}
	}
	
	// 解析结束，释放资源
	scpi_parse_finish(yybuf);
	return err;
}

const char* scpi_get_last_error() {
	return _err_buf;
}

void scpi_test_main(int argc, char **argv) {
	printf("Total rules count: %d\r\n", YY_NUM_RULES);
	while(yylex() >= 0);
	//yywrap();
}
